package tools.clustering;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;

import tools.utils.DoubleMatrix;

public class NearestNeighbor {

	private HashMap<String,Integer> id2nr;
	private HashMap<Integer, String> nr2id;
	private DoubleMatrix matrix;
	private ArrayList<cluster> clusters;
	
	public static void main(String[] args)throws Exception{
		if(args.length>0){
			if(args[0].equals("cluster")){
				if(args.length==4){
					NearestNeighbor nn=new NearestNeighbor(args[1]);
					nn.NNcluster(Integer.parseInt(args[2]),args[3]);
				}else{
					System.out.println(printHelp());
					System.exit(616);
				}
			}else{
				System.out.println(printHelp());
				System.exit(616);
			}
		}else{
			System.out.println(printHelp());
			System.exit(616);
		}
	}
	
	private static String printHelp(){
		String help="cluster <distance_matrix_file> <nr of clusters> <outPrefix>\n";
		
		return help;
	}
	
	public NearestNeighbor(String DistanceMatrixFile)throws Exception{
		BufferedReader in=new BufferedReader(new FileReader(DistanceMatrixFile));
		String[] l=in.readLine().split("\t");
		id2nr=new HashMap<String, Integer>();
		nr2id=new HashMap<Integer, String>();
		for (int i = 0; i < l.length; i++) {
			id2nr.put(l[i], i);
			nr2id.put(i, l[i]);
		}
		matrix=new DoubleMatrix(id2nr.size(),id2nr.size());
		clusters=new ArrayList<cluster>();
		int i=0;
		for(String s=in.readLine();s!=null;s=in.readLine(),i++){
//			System.out.println(i);
			if(s.length()>0){
				l=s.split("\t");
				clusters.add(new cluster(i,id2nr.size()));
				for (int j = 0; j < l.length; j++) {
					matrix.set(i, j, Double.parseDouble(l[j]));
				}
			}
		}
//		System.out.println(matrix.toString());
	}
	
	public void NNcluster(int k,String outPrefix)throws Exception{
		System.out.println("clustering...");
		int x,y;
		double max;
		BufferedWriter order=new BufferedWriter(new FileWriter(outPrefix+"_order.csv"));
		for(;clusters.size()>k;){
//			System.out.println(clusters.size());
			x=-1;
			y=-1;
			max=Double.NEGATIVE_INFINITY;
			//find max
//			System.out.println("clustersize: "+clusters.size());
			for (int i0=0;i0<clusters.size();i0++) {
//				System.out.println(i0);
				cluster i=clusters.get(i0);
//				System.out.println(i.getClusterNr());
				for (int j0=0;j0<i0;j0++) { //assumes a symmetric distance matrix
//					System.out.println(i0+"\t"+j0);
//					System.out.println(i.getColumn(clusters.get(j0).getClusterNr())+"\t"+ clusters.get(j0).getClusterNr());
//					System.out.println(matrix.get(i.getColumn(clusters.get(j0).getClusterNr()),clusters.get(j0).getClusterNr()));
					if(max<matrix.get(i.getColumn(clusters.get(j0).getClusterNr()),clusters.get(j0).getClusterNr())){
						x=i0;
						y=j0;
						max=matrix.get(i.getColumn(clusters.get(j0).getClusterNr()),clusters.get(j0).getClusterNr());
					}
				}
			}
//			if(max==-616){
//				break;
//			}
			//merge cluster x with cluster y
			if(x>=0&&y>=0){
				cluster keep=clusters.get(x);
				cluster discard=clusters.get(y);
				clusters.remove(discard);
				order.write(nr2id.get(keep.getClusterNr())+"\t"+nr2id.get(discard.getClusterNr())+"\t"+max+"\n");
				keep.addInhabitants(discard.getInhabitants());
				for (cluster i : clusters) {
					//complete linkage
					if(matrix.get( keep.getColumn(i.getClusterNr()),i.getClusterNr())>matrix.get( discard.getColumn(i.getClusterNr()),i.getClusterNr())){
						keep.setColumnIndex(i.getClusterNr(), discard.getColumn(i.getClusterNr()));
					}
				}
			}
//			for (cluster i : clusters) {
//				System.out.println("cluster "+i.getClusterNr());
//				for(int j=0;j<id2nr.size();j++){
//					System.out.println(i.getColumn(j));
//				}
//			}
		}
		order.close();
		//print clusters
		BufferedWriter cluster=new BufferedWriter(new FileWriter(outPrefix+"_cluster.csv"));
		System.out.println(clusters.size());
		for (cluster i : clusters) {
			for (Integer j : i.getInhabitants()) {
				cluster.write(nr2id.get(j)+"\t"+i.getClusterNr()+"\n");
			}
		}
		cluster.close();
	}
	
	
	class cluster{
		private int clusterNr;
		private int[] columnIndices;
		private ArrayList<Integer> inhabitants;
		
		/**
		 * 
		 * @param clusterNr
		 * @param datasize
		 */
		public cluster(int clusterNr,int datasize){
			this.clusterNr=clusterNr;
			columnIndices=new int[datasize];
			for (int i = 0; i < columnIndices.length; i++) {
				columnIndices[i]=clusterNr;
			}
			inhabitants=new ArrayList<Integer>();
			inhabitants.add(clusterNr);
		}
		public void setColumnIndex(int row,int column){
			columnIndices[row]=column;
		}
		public void addInhabitants(ArrayList<Integer> i){
			inhabitants.addAll(i);
		}
		public void addInhabitants(Integer i){
			inhabitants.add(i);
		}

		public int getClusterNr() {
			return clusterNr;
		}
		public int getColumn(int Row){
			return columnIndices[Row];
		}
		public ArrayList<Integer> getInhabitants(){
			return inhabitants;
		}
	}
}
